#pragma once

#include "../Core/Log.h"
#include "../Core/DateTime.h"

template <class T>
class ADSBMap : public std::map<unsigned short, T*>
{
protected:
	bool m_OverWrite;
	unsigned short m_IteratorTrackNumber;
	std::map<unsigned int, unsigned short> m_TargetAdressMaps;

public:
	ADSBMap(bool p_OverWrite = true)
	{
		m_OverWrite = p_OverWrite;
		m_IteratorTrackNumber = 1;
	}

	virtual ~ADSBMap()
	{
	}

	virtual unsigned short GetAvailableTrackNumber()
	{
		while(std::map<unsigned short, T*>::count(m_IteratorTrackNumber)!=0)
		{
			m_IteratorTrackNumber++;
		}
		return m_IteratorTrackNumber;
	}

	T* GetTrack(unsigned short p_TrackNumber)
	{
		if(std::map<unsigned short, T*>::count(p_TrackNumber)!=0)
		{
			return std::map<unsigned short, T*>::find(p_TrackNumber)->second;
		}
		return NULL;
	}

	virtual void Preprocess(T& p_T)	{}

	virtual bool BeforeDelete(T& p_T) = 0;

	virtual bool BeforeUpdate(T& p_T) = 0;

	virtual void AfterUpdate(T& p_T) = 0;

	void Update(T* p_T)
	{
		if(p_T==NULL)
		{
			return;
		}
		Preprocess(*p_T);
		if(p_T->IsDeleteMessage() && BeforeDelete(*p_T))
		{
			unsigned short t_TrackNumber = p_T->GetTrackNumber();
			T* t_T = GetTrack(t_TrackNumber);
			if((t_TrackNumber!=0) && (t_T!=NULL) && BeforeDelete(*t_T))
			{
				std::map<unsigned short, T*>::erase(t_TrackNumber);
				delete t_T;
			}
			delete p_T;
			return;
		}
		if(!BeforeUpdate(*p_T))
		{
			delete p_T;
			return;
		}
		
		/* Generate List Track Number from Target Address */
		unsigned int t_TargetAddress = p_T->GetTargetAddress();		
// 		unsigned short t_TrackNumber = p_T->GetTrackNumber();
		unsigned short t_TrackNumber = 0;		
		std::map<unsigned int, unsigned short>::iterator t_TrackNumberItr;
		
		t_TrackNumberItr = m_TargetAdressMaps.find(t_TargetAddress);
		if(t_TrackNumberItr != m_TargetAdressMaps.end())		// Jika sudah ada maka ambil TrackNumbernya
		{
		    t_TrackNumber = t_TrackNumberItr->second;		    
		}
		else						// Jika belum ada maka Generate new TrackNumber
		{
		    t_TrackNumber = GetAvailableTrackNumber();
		    p_T->SetTrackNumber(t_TrackNumber);
		    m_TargetAdressMaps.insert(std::pair<unsigned int, unsigned short>(t_TargetAddress, t_TrackNumber));
		}
		
// 		if(t_TrackNumber==0)
// 		{
// 			t_TrackNumber = GetAvailableTrackNumber();
// 			p_T->SetTrackNumber(t_TrackNumber);
// 			std::cout << "Set Track Number to " << t_TrackNumber << std::endl;
// 		}

		T* t_T = GetTrack(t_TrackNumber);
		if(t_T!=NULL)
		{
			if(m_OverWrite)
			{
				t_T->Reset();
				t_T->SetTrackNumber(t_TrackNumber);
			}
			t_T->SynchronizeFrom(*p_T);
			t_T->GetTimeStamp().Tick();
			delete p_T;
			p_T = t_T;
// 			std::cout << "Delete Track" << std::endl;
		}
		else
		{
			std::cout << "Inserting ADS-B Track with TrackAddress = " << t_TargetAddress << " and TrackNumber = " << t_TrackNumber << std::endl;
			std::map<unsigned short, T*>::insert(std::pair<unsigned short, T*>(t_TrackNumber, p_T));
		}
		AfterUpdate(*p_T);
	}

	virtual void CleanUp()
	{
		std::vector<unsigned short> t_ToBeDeleted;
		for(typename std::map<unsigned short, T*>::iterator p=std::map<unsigned short, T*>::begin(); p!=std::map<unsigned short, T*>::end(); p++)
		{
		    if(p->second->IsExpired() || p->second->IsDeleteMessage())
			{
				t_ToBeDeleted.push_back(p->first);
			}
		}
		for(std::vector<unsigned short>::iterator p=t_ToBeDeleted.begin(); p!=t_ToBeDeleted.end(); p++)
		{
			T* t_T = GetTrack(*p);
			if((t_T!=NULL) && BeforeDelete(*t_T))
			{
				std::map<unsigned short, T*>::erase(*p);
				delete t_T;
			}
		}
	}

	virtual void Save(std::string p_FileName)
	{
		try
		{
			std::ofstream t_File(p_FileName.c_str());
			if(!t_File) throw std::exception();
			RawData t_RawData;
			t_RawData.WriteShort((unsigned short)std::map<unsigned short, T*>::size());
			t_RawData.WriteToOStream(t_File, 2);
			DateTime t_DateTime;
			t_RawData.WriteLong(t_DateTime.GetTotalSeconds());
			t_RawData.WriteToOStream(t_File, 4);
			for(typename std::map<unsigned short, T*>::iterator p=std::map<unsigned short, T*>::begin(); p!=std::map<unsigned short, T*>::end(); p++)
			{
				t_RawData.Reset();
				t_RawData.WriteShort(p->second->GetLength());
				p->second->WriteRawData(t_RawData);
				t_RawData.WriteToOStream(t_File, t_RawData.GetLength());
			}
			t_File.close();
			std::ostringstream t_Log;
			t_Log << "Saving [" << (int)std::map<unsigned short, T*>::size() << "] data to file [" << p_FileName << "] OK";
			Log::GetSingleton().Write(LogLevel_Info, t_Log.str());
		}
		catch(...)
		{
			Log::GetSingleton().Write(LogLevel_Error, "Error while saving data to file [" + p_FileName + "]");
		}
	}

	virtual void Load(std::string p_FileName)
	{
		try
		{
			std::ifstream t_File(p_FileName.c_str());
			if(!t_File) throw std::exception();
			RawData t_RawData;
			t_RawData.ReadFromIStream(t_File, 2);
			unsigned short t_Count = t_RawData.ReadShort();
			t_RawData.ReadFromIStream(t_File, 4);
			DateTime t_Now;
			DateTime t_DateTime(t_RawData.ReadLong());
			if(t_Now.Delta(t_DateTime)>300)
			{
			    #ifndef _DEBUG
				Log::GetSingleton().Write(LogLevel_Warning, "Time stamp of file [" + p_FileName + "] more than 5 minutes - ignoring");
				return;
				#endif
			}
			for(unsigned short i=0; i<t_Count; i++)
			{
				t_RawData.Reset();
				t_RawData.ReadFromIStream(t_File, 2);
				unsigned short t_Length = t_RawData.ReadShort();
				t_RawData.Reset();
				t_RawData.ReadFromIStream(t_File, t_Length);
				T* t_T = new T();
				t_T->ReadRawData(t_RawData);
				std::map<unsigned short, T*>::insert(std::pair<unsigned short, T*>(t_T->GetTrackNumber(), t_T));
			}
			t_File.close();
			std::ostringstream t_Log;
			t_Log << "Loading [" << (int)t_Count << "] data from file [" << p_FileName << "] OK";
			Log::GetSingleton().Write(LogLevel_Info, t_Log.str());
		}
		catch(...)
		{
			Log::GetSingleton().Write(LogLevel_Error, "Error while loading data from file [" + p_FileName + "]");
		}
	}
};
